/**
 * 
 */
package org.stars.daostars.core.parser;

import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;

import org.stars.dao.exception.parser.CircularDependencyException;
import org.stars.daostars.core.SqlQuery;
import org.stars.daostars.exceptions.parser.QueryNotFoundException;
import org.stars.daostars.sqlmapper.SqlMapper;
import org.stars.util.StringUtility;

/**
 * Per ogni query appartenente alla definizione del dao, si ricerca la query
 * padre e si inserisce il codice sql prima del codice sql della query figlia.
 * 
 * @author Francesco Benincasa (908099)
 * 
 */
public abstract class FindParentQuery {

	/**
	 * Per ogni query appartenente alla definizione del dao, si ricerca la query
	 * padre e si inserisce il codice sql prima del codice sql della query
	 * figlia.
	 * 
	 * @param definition
	 *            definizione del dao.
	 * @return <code>true</code> in caso di errore
	 * @throws Exception
	 *             in caso di errore
	 */
	public static boolean execute(SqlMapper definition) throws Exception {
		// pila dei dao analizzati.
		Stack<String> stack = new Stack<String>();
		Set<String> processedQuery = new TreeSet<String>();

		for (SqlQuery sqlQuery : definition.getQuerySet().values()) {
			if (!processedQuery.contains(sqlQuery.getName())) {
				try {
					findParent(stack, sqlQuery, definition, processedQuery);
				} catch (Exception e) {
					throw (e);
				}
			}
		}

		return true;
	}

	/**
	 * Metodo usato per cercare ricorsivamente le definizioni delle varie query
	 * e di risolvere ricorsivamente le dipendenze tra le varie query.
	 * <p>
	 * Per evitare di entrare in loop infiniti, è stata introdotto lo stack, in
	 * modo da poter sempre avere sottocontrollo le query attualmente
	 * analizzate.
	 * 
	 * @param stack
	 *            stack delle query attualmente analizzate.
	 * @param sqlQuery
	 *            oggetto DaoSqlQuery da analizzare.
	 * @param dao
	 *            definizione del dao attualmente in analisi.
	 * @param processedQueries
	 *            definizione delle query già processate.
	 * @return <code>true</code>.
	 * @throws Exception
	 *             Se qualcosa va male, viene sollevata un'eccezione.
	 */
	private static boolean findParent(Stack<String> stack, SqlQuery sqlQuery, SqlMapper dao, Set<String> processedQueries) {

		if (processedQueries.contains(sqlQuery.getName()))
			return true;

		// get query sql
		String sApp = StringUtility.nvl(sqlQuery.getPlainSql());
		String sBaseSqlName = StringUtility.nvl(sqlQuery.getBaseSqlQueryName());

		if ("".equalsIgnoreCase(sBaseSqlName)) {
			// non ha padre, quindi lo inseriamo nello stack e basta.
			processedQueries.add(sqlQuery.getName());
			return true;
		} else {
			if (stack.contains(sqlQuery.getName())) {
				String msg = "Dao " + dao.getName() + ", query " + sqlQuery.getName()+ ": Found circular definition for the query.";
				throw (new CircularDependencyException(msg));
			}

			stack.push(sqlQuery.getName());

			SqlQuery fatherSql = dao.getQuery(sBaseSqlName);
			if (fatherSql == null) {
				String msg = "Dao " + dao.getName() + ", query " + sqlQuery.getName()+ ": The parent query "+sBaseSqlName+" does not exist in the dao.";
				throw (new QueryNotFoundException(msg));
			}
			findParent(stack, fatherSql, dao, processedQueries);

			// sql
			sqlQuery.setPlainSql(fatherSql.getPlainSql() + " " + sApp);

			// impostiamo la mappa di conversione se non e' stato definito alcuna
			// mappa di conversione
			if (sqlQuery.getConversionMapName().length() == 0 && fatherSql.getConversionMapName().length() > 0) {
				sqlQuery.setConversionMapName(fatherSql.getConversionMapName());
			}

			processedQueries.add(sqlQuery.getName());

			stack.pop();
			return true;
		}
	}
}
